package Bilibili;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import util.PythonUtil;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.nio.file.Files;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import static json.ParseJsonUtil.readFileContent;

/**
 * 3分钟教你如何下载B站视频 https://www.bilibili.com/read/cv1422476/
 * win10应用商店上下载客户端 bilibili UWP
 * b站视频下载修改视频名称工具类（java） https://blog.csdn.net/jiarui_mama/article/details/113069238
 * B站 bilibili 视频下载方法 批量重命名工具（Python） https://www.bilibili.com/read/cv6626120
 * https://github.com/gooin/bilibili_video_rename_tool
 *
 * java实现音视频的合并 https://blog.csdn.net/maziaotong/article/details/127863727
 */
public class BilibiliUtilVideoUWP {

    // 视频下载位置
    private static final String videoDownloadPath = "D:\\bilibiliload\\967612226";
    // ffmpeg exe路径，如果配置了环境变量，只写名称即可，如果没有配置，就写全路径
    private static final String ffmpegExePath = "ffmpeg";

    public static void main(String[] args) throws Exception {
        // 根目录下 967612226.dvi cover.jpg desktop.ini
        // 分p目录下
        // 目前看到有3种格式
        //  1.音画分开MP4
        //     D:\bilibiliload\967612226\290 967612226.info 967612226_290.xml audio1.mp4 video.mp4
        //     "Format": 2,
        //     "TotalParts": 2,
        //     "IsDash": true,
        //     "IsMerged": false,
        //  2.分段flv
        //     D:\bilibiliload\17222411\1 17222411.info 17222411_1.xml 17222411_1_0.flv 17222411_1_1.flv
        //     "Format": 1,
        //     "TotalParts": 2,
        //  3.完整合并的MP4
        //     D:\bilibiliload\333024338\1 333024338.info 333024338_1.xml 333024338_1_0.mp4
        //     "Format": 2,
        //     "TotalParts": 1,
        //     "IsMerged": true,

        // String filepath = "D:\\bilibiliload\\333024338"; // 完整视频MP4
        String filepath = videoDownloadPath; // 音画分开MP4
        // String filepath = "D:\\bilibiliload\\17222411"; // 分段flv 一般6分钟一段
        File file = new File(filepath);
        if (!file.exists() || !file.isDirectory()) {
            System.out.println("请指定正确的目录");
            return;
        }
        File[] files = file.listFiles();

        // 根目录下获取视频名称 .dvi
        File[] dviFiles = file.listFiles((dir, name) -> name.endsWith(".dvi"));
        if (dviFiles == null || dviFiles.length == 0) {
            System.out.println("不存在指定后缀的文件");
            return;
        }
        File dviFile = dviFiles[0];
        if (!dviFile.isFile()) {
            System.out.println("不存在指定后缀的文件");
            return;
        }
        String jsonString = readFileContent(dviFile);
        JSONObject jsonObject = JSON.parseObject(jsonString);

        // {"SeasonId":null,"EpisodeId":null,"IsBangumi":false,"CoverWidth":192.0,
        // "CoverHeight":120.0,"Title":"网易云课堂 - uni-app实战仿糗事百科app开发(上)",
        // "IsSelected":false,"IsLastHit":false,"Aid":"71415770","Uploader":"_程序狗",
        // "Mid":"232699246","Description":"https://study.163.com/\n#若有冒犯,请联系我删除\n网易云课堂 - uni-app实战仿糗事百科app开发\n课程资料:链接：https://pan.baidu.com/s/1FNG6R6wS1cYH3kxUPwZOGA \n提取码：j0hu \n复制这段内容后打开百度网盘手机App，操作更方便哦",
        // "CoverURL":"http://i1.hdslb.com/bfs/archive/2da3ceb0dcac3a352d0f407c824375f12693bc03.jpg",
        // "CoverImageSource":null,"Tag":null,"DownloadTimeRelative":161928671,"DownloadTime":null,
        // "CreateDate":"2019-10-16 17:13","TotalTime":"00:00:00","Parts":null,"FontSize6":10.0,
        // "FontSize8":12.0,"FontSize10":14.0,"FontSize12":16.0,"FontSize14":18.0,"FontSize16":20.0,
        // "FontSize18":22.0,"FontSize20":24.0,"FontSize22":26.0,"FontSize24":28.0,"FontSize30":34.0,
        // "FontSize34":38.0}
        String title = jsonObject.getString("Title");
        if (StringUtils.isBlank(title)) {
            System.out.println("视频名称为null");
            return;
        }

        // 复制到一个新的目录下
        // File parentFile = file.getParentFile();
        File targetFile = new File(file, title);
        // 已存在同名目录处理，在文件夹名称后面+副本
        while (true) {
            if (!targetFile.exists() || !targetFile.isDirectory()) {
                // 创建目录
                // targetFile.mkdir(); // 只能创建一层目录
                targetFile.mkdirs(); // 创建多层目录，包括创建必需但不存在的父目录
                break;
            }
            title += "-副本";
            targetFile = new File(file, title);
        }
        // stream流排序 https://blog.csdn.net/qq_36763236/article/details/111469653
        List<File> fileList = Arrays.stream(files)
                .filter(f -> isP(f))
                .sorted(Comparator.comparing(f -> Integer.parseInt(f.getName())))
                .collect(Collectors.toList());

        if (CollectionUtils.isNotEmpty(fileList)) {
            for (File dir : fileList) {
                // 获取分P标题
                // 333024338.info 333024338_1.xml 333024338_1_0.mp4

                // {"Type":0,"Aid":"333024338","Bid":"BV17A411G7aL","Cid":"337618791","SeasonId":null,
                // "EpisodeId":null,"Title":"【计算机-Java】Java避坑指南，致经常写bug的你","Uploader":"你的知识书架",
                // "Description":"转自网络\r\n用于个人学习研究，如有侵权请联系删除\r\n看了这个指南，减少debug的时间",
                // "CoverURL":"http://i2.hdslb.com/bfs/archive/ed96053469cda4829376e74ba4ea79c1bee2fe43.png",
                // "Tag":null,"From":"vupload","PartNo":"1","PartName":"1-1 为什么做这门课","Format":2,"TotalParts":1,
                // "DownloadTimeRelative":209659970,"CreateDate":"2021-05-13 22:49",
                // "TotalTime":"00:05:19.3140000","PartTime":[319314],
                // "TotalSizeByte":10571397,"TotalSizeString":null,"IsSinglePart":false,"IsDash":false,
                // "IsMerged":true,"VideoInfo":{"MediaType":0,"CodecId":7,"CodecName":"avc1.640028","VideoWidth":1280,
                // "VideoHeight":720,"Bandwidth":138950,"FrameRate":24.3902},"AudioInfo":[{"MediaType":1,"CodecId":0,
                // "CodecName":"mp4a.40.2","VideoWidth":0,"VideoHeight":0,"Bandwidth":122829,"FrameRate":0.0}]}
                File[] pFiles = dir.listFiles();
                // 视频文件 mp4 flv
                List<File> videoFileList = new ArrayList<>();
                // 视频信息文件 .info
                File partNameFile = null;

                for (File pFile : pFiles) {
                    if (!pFile.isFile()) {
                        continue;
                    }
                    if (pFile.getName().endsWith(".info")) {
                        partNameFile = pFile;
                    }
                    if (pFile.getName().endsWith(".mp4") || pFile.getName().endsWith(".flv")) {
                        // audio1.mp4 video.mp4 音频和视频都是MP4
                        videoFileList.add(pFile);
                    }
                }

                if (partNameFile != null && CollectionUtils.isNotEmpty(videoFileList)) {
                    String partNameJsonString = readFileContent(partNameFile);
                    JSONObject partNameJsonObject = JSON.parseObject(partNameJsonString);

                    // 分p名称
                    String partName = partNameJsonObject.getString("PartName");
                    String PartNo = partNameJsonObject.getString("PartNo");
                    // 视频格式 1：flv 2：MP4
                    int format = partNameJsonObject.getIntValue("Format");
                    // 视频文件个数 flv：未合并  MP4：音频视频分离
                    int totalParts = partNameJsonObject.getIntValue("TotalParts");
                    // 是否合并
                    Boolean isMerged = partNameJsonObject.getBoolean("IsMerged");

                    if (format == 2 && isMerged != null && isMerged) {
                        // 完整视频 Mp4
                        if (StringUtils.isNotBlank(partName)) {
                            File videoFile = videoFileList.get(0);
                            // copy文件
                            // 4种Java文件复制的方法 https://blog.csdn.net/qq_30436011/article/details/127490081
                            File dest = new File(targetFile, partName + ".mp4");
                            Files.copy(videoFile.toPath(), dest.toPath());

                            // 测试时你，可以先只处理一个
                            // break;
                        }
                    }

                    if (format == 2 && isMerged != null && !isMerged) {
                        // 音频视频分离
                        if (StringUtils.isNotBlank(partName)) {
                            if (videoFileList.size() > 2) {
                                System.out.println("视频文件存在多个: " + dir.getAbsolutePath());
                                continue;
                            }
                            // audio1.mp4 video.mp4
                            String audioPath = null; // 音频文件路径
                            String videoPath = null; // 视频文件路径
                            File file1 = videoFileList.get(0);
                            File file2 = videoFileList.get(1);
                            if (file1.getName().startsWith("audio")) {
                                audioPath = file1.getAbsolutePath();
                                videoPath = file2.getAbsolutePath();
                            } else if (file1.getName().startsWith("video")) {
                                audioPath = file2.getAbsolutePath();
                                videoPath = file1.getAbsolutePath();
                            }

                            File dest = new File(targetFile, partName + ".mp4");
                            // 合并音视频
                            // 全网最强电商教程《谷粒商城》对标阿里P6/P7，40-60万年薪
                            // 文件名中有/，会创建2层文件夹
                            // 是Windows会给出提示：“\”符号是不能作为文件或者文件夹的名字的
                            // Windows文件名中不能有下列符号：“?”、“、”、“╲”、“/”、“*”、““”、“”“、“<”、“>”、“|”。
                            // https://blog.csdn.net/flf1234567898/article/details/108518043
                            ffmpegMerge(videoPath, audioPath, dest.getAbsolutePath());

                            // 测试时你，可以先只处理一个
                            // break;
                        }
                    }

                    // ffmpeg将多个flv文件合成为mp4
                    if (format == 1) {
                        // 分段flv
                        if (StringUtils.isNotBlank(partName)) {
                            // 17222411_2_0.flv 17222411_2_1.flv 17222411_2_2.flv
                            videoFileList = videoFileList.stream()
                                    .sorted(Comparator.comparing(f ->
                                            // java.lang.NumberFormatException: For input string: "17222411101" 超过intger最大值
                                            // Integer.parseInt(f.getName().substring(0, f.getName().lastIndexOf(".")).replace("_", ""))))
                                            Integer.parseInt(f.getName().substring(f.getName().indexOf("_") + 1, f.getName().lastIndexOf(".")).replace("_", ""))))
                                    .collect(Collectors.toList());

                            if (videoFileList.size() == 1) {
                                // 单个文件将flv转换为MP4
                                // java 使用ffmpeg 处理视频 卡住问题 https://www.cnblogs.com/wangxiaomei/p/16573409.html
                                // 如果文件重名，就一直在等待 回执 加-y 可以覆盖
                                // File dest = new File(targetFile, partName + ".mp4");
                                File dest = new File(targetFile, "(" + PartNo + ")"+ partName + ".mp4");
                                if (dest.exists()) {
                                    dest = new File(targetFile, "(" + PartNo + ")"+ partName + "-重名" + System.currentTimeMillis() + ".mp4");
                                }

                                boolean res = ffmpegFlv2Mp4(videoFileList.get(0).getAbsolutePath(), dest.getAbsolutePath());
                                if (!res) {
                                    System.out.println("脚本文件执行失败，请手动处理");
                                    break;
                                }
                                continue;
                            }

                            String str = "";
                            for (File flvFile : videoFileList) {
                                if (StringUtils.isNotBlank(str)) {
                                    str += "\n";
                                }
                                str += "file '" + flvFile.getAbsolutePath() + "'";
                            }

                            System.out.println("flv个数：" + videoFileList.size());
                            // 保存文件名到目标文件 给ffmpeg调用
                            File listFile = new File(file, "list.txt");
                            String listFilePath = listFile.getAbsolutePath();
                            // 是覆盖，还是追加？？？ 是覆盖
                            writeFile(str, listFilePath);

                            File dest = new File(targetFile, partName + ".mp4");

                            boolean res = ffmpegMergeFlv(listFilePath, dest.getAbsolutePath());
                            if (!res) {
                                System.out.println("脚本文件执行失败，请手动处理");
                                break;
                            }

                            // 测试时你，可以先只处理一个
                            // break;
                        }
                    }

                }

            }
        }

    }

    /**
     * 是否为分P目录
     */
    private static boolean isP(File file) {
        if (file == null || !file.isDirectory()) {
            return false;
        }
        String name = file.getName();
        // java判断字符串是否为纯数字 https://jingyan.baidu.com/article/c74d6000d721f50f6b595d5e.html
        return name.matches("[0-9]+");
    }

    /**
     * 将内容写到目标文件
     */
    private static void writeFile(String newContent, String targetFile) throws Exception {
        File file = new File(targetFile);
        FileOutputStream fos = new FileOutputStream(file);
        OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
        osw.write(newContent);
        osw.flush();
        osw.close();
        fos.close();
    }

    /**
     * java实现音视频的合并
     * https://blog.csdn.net/maziaotong/article/details/127863727
     * 使用 ffmpeg 来合并音频和视频文件 https://www.cnblogs.com/dragona/p/17206097.html
     *
     * @param videoFilePath 原视频路径
     * @param audioFilePath 原视频路径
     * @param desFilePath   合并后视频存放路径+视频名称
     */
    public static void ffmpegMerge(String videoFilePath, String audioFilePath, String desFilePath) {
        // ffmpeg -i 原视频路径 -i 原音频路径 -codec copy 合并后视频存放路径+视频名称

        // 解释器
        String exe = ffmpegExePath;
        // 组合成一个字符串数组
        // 执行报错：Unrecognized option 'codec copy' https://www.jianshu.com/p/ea996d79a231
        // String[] cmdArr = new String[]{exe, "-i", videoFilePath, "-i", audioFilePath, "-codec copy", desFilePath};
        // 解决报错："-codec copy" 要拆开，数组元素不带空格
        String[] cmdArr = new String[]{exe, "-i", videoFilePath, "-i", audioFilePath, "-codec", "copy", desFilePath};

        PythonUtil.executePythonScript(cmdArr);

        String cmdStr = MessageFormat.format(exe + " -i {0} -i {1} -codec copy {2}", videoFilePath, audioFilePath, desFilePath);
        System.out.println("cmdStr: " + cmdStr);
        // PythonUtil.executeCmd(cmdStr);

    }

    /**
     * 使用ffmpeg批量合并flv文件
     * ffmpeg -f concat -i mylist.txt -c copy output.flv
     * https://www.likecs.com/show-379618.html
     * ffmpeg将多个flv文件合成为mp4（python版） https://www.jianshu.com/p/bfda71bad87d
     * 通过ffmpeg将FLV文件转换为MP4 https://blog.csdn.net/CN_Sue/article/details/122290494
     *
     * @param desFilePath 合并后视频存放路径+视频名称
     */
    public static boolean ffmpegMergeFlv(String listFilePath, String desFilePath) {
        // 拼接完，只有第一个可以播放
        // ffmpeg -i "concat:17222411_2_0.flv|17222411_2_1.flv|17222411_2_2.flv" -codec copy out_mp4.mp4
        // 3个都可以播放
        // ffmpeg -f concat -i list.txt -codec copy out_mp42.mp4

        // PythonUtil.executePythonScript(cmdArr);

        // 解决报错：-safe 0
        // https://www.jianshu.com/p/ef54a7cdcb7f
        // https://www.cnblogs.com/han-1034683568/p/11515459.html
        // [concat @ 000001bbe578d3c0] Unsafe file name 'D:bilibiliload17222411117222411_1_0.flv'
        // [in#0 @ 000001bbe5777500] Error opening input: Operation not permitted

        // 解决报错：文件路径加单引号
        // Impossible to open 'D:bilibiliload17222411117222411_1_0.flv'

        // -loglevel quiet 如果报错可以删掉，放出日志
        // ffmpeg设置终端不显示日志 https://www.cnblogs.com/chentiao/p/17174382.html
        String cmdStr = MessageFormat.format(ffmpegExePath + " -loglevel quiet -f concat -safe 0 -i {0} -codec copy {1}", listFilePath, desFilePath);
        System.out.println("cmdStr: " + cmdStr);
        return PythonUtil.executeCmd(cmdStr);
    }

    /**
     * 通过ffmpeg将FLV文件转换为MP4
     * ffmpeg -i "input.flv" -c copy "output.mp4"
     * https://blog.csdn.net/CN_Sue/article/details/122290494
     * @param videoFilePath 要转换的flv视频
     * @param desFilePath 合并后视频存放路径+视频名称
     */
    public static boolean ffmpegFlv2Mp4(String videoFilePath, String desFilePath) {
        // 文件名有空格 需要加引号
        // String cmdStr = MessageFormat.format("ffmpeg -loglevel quiet -i \"{0}\" -codec copy \"{1}\"", videoFilePath, desFilePath);
        String cmdStr = MessageFormat.format(ffmpegExePath + " -i \"{0}\" -codec copy \"{1}\"", videoFilePath, desFilePath);
        System.out.println("cmdStr: " + cmdStr);
        return PythonUtil.executeCmd(cmdStr);
    }

}
